Day 07 轉出的 GLB 是半成品,還沒瘦身。今天處理面數(三角形數量)的問題:真實機械零件動輒幾萬、幾十萬個三角形,瀏覽器會卡住,需要減面(decimation)——用較少的三角形逼近原本的形狀。
不是。Day 07 的「拆食譜、打包」是 cad-ingest 這個工班做的;瘦身是另一個工班:mesh-forge。它接手的時候,完全不知道自己拿到的是機械零件還是一面牆——它眼裡只有「一堆有名字的三角形貼片」。也因為它不知道場景是什麼,之後不管是機械、建築,甚至以後加的新場景,都能共用同一套瘦身邏輯(這正是 Day 02 講過的「生產線不知道自己在服務誰」)。
mesh-forge 接手 raw.glb 之後,先後做兩件不同的事:
raw.glb ──① 打掃──▶ normalized.glb ──② 瘦身──▶ lod1.glb
① 打掃(normalize):這一步還不減任何三角形,只是把東西「擺整齊」——把本來該接在一起、卻因為轉檔而微微分開的頂點重新黏起來(焊接)、把翻錯方向的表面翻回正確的朝外方向(修法線)、把整台機器水平置中、垂直方向讓它「站在地上」(最低點對齊高度 0)。做完這步,形狀還是原本的形狀,只是乾淨、擺正了。
② 瘦身(decimate):這一步才真的在減三角形——用 Day 08 前半段講的 water-filling,先幫每個零件分配「可以留幾個三角形」的預算,再依照這個預算把每個零件的三角形數量壓下去。
兩步分開做是有道理的:打掃如果沒做好(頂點沒黏好),瘦身演算法判斷「這裡能不能合併」時會出錯,瘦身效果反而變差。所以永遠是先打掃、再瘦身。
這兩步各自是一個可以獨立執行的小工具,會依序被呼叫、一步接一步跑:第一次呼叫做「打掃」,把結果交給第二次呼叫做「瘦身」。實際下指令的方式留到技術版再看。
最直覺的做法是「每個零件都減掉同樣的比例」——例如全部砍掉九成的三角形。問題是機殼可能有幾萬個三角形、小螺絲可能只有一百個,砍掉九成,機殼還剩幾千個,螺絲卻只剩十個,甚至因為零頭被無條件捨去而直接消失。網頁打開一看,少了幾顆螺絲,像是壞掉了。
比較公平的做法叫 water-filling(灌水法):想像把「總共可以用的三角形數」當作水,倒進一排高低不同的杯子(每個零件)。矮杯子(小零件)先被填滿到一個保底線(下限,例如至少 64 個面),水位持續上升,滿了的杯子不再多倒;剩下的水,才按杯子的「塊頭」(體積)比例,流進還沒滿的大杯子。
這件事不是算一次就結束:一旦幾個零件被鎖在保底線,剩下能分配的預算就變少,其他零件該分到多少也要重新算——所以要反覆收斂,直到每個零件的分配都穩定下來。
Day 06 那台減速機三角形數只有 3,300 出頭,不管目標設多少,這一步幾乎沒事做——沒有真的觸發減面。要驗證「小零件不會被砍到消失」,得另外現場組一個「一個大殼體 + 幾顆小零件」的假模型,故意讓總面數超過目標,才測得出 water-filling 有沒有真的生效。這也是後面幾篇會一直遇到的模式:自己畫的乾淨樣本適合當「格式對不對」的煙霧測試,真正的演算法邊界案例,往往要另外造資料才測得到。
還不是。mesh-forge 吐出來的檔案,方向、單位都還是 Day 06、07 那套內部使用的規格(不是瀏覽器慣用的那套),也還沒經過壓縮。真正要出貨、給瀏覽器載入的那個檔案,會在下一篇的「打包」工班手上完成——那才是整條生產線最後的成品。
下一篇接著講:瘦身完的檔案,怎麼壓縮成真正輕巧的出貨包裹。